package com.gitee.l0km.codegen.thrift;

import java.io.File;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;
import org.apache.commons.configuration2.Configuration;
import org.apache.commons.configuration2.PropertiesConfiguration;
import org.apache.commons.configuration2.builder.FileBasedConfigurationBuilder;
import org.apache.commons.configuration2.io.FileHandler;

import com.gitee.l0km.codegen.base.CodeGenUtils;
import com.gitee.l0km.codegen.base.generator.CodeWriter;
import com.gitee.l0km.codegen.base.generator.CxxCodeWriter;
import com.gitee.l0km.codegen.base.generator.GeneratorConfiguration;
import com.gitee.l0km.codegen.base.generator.JavaCodeWriter;
import com.gitee.l0km.com4j.base.ConditionChecks;
import com.gitee.l0km.com4j.base.MiscellaneousUtils;
import com.gitee.l0km.com4j.base.SimpleLog;
import com.gitee.l0km.javadocreader.JavadocReader;
import static com.gitee.l0km.xthrift.base.BaseThriftUtils.isThriftStruct;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Strings;
import com.google.common.base.Throwables;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;

/**
 * decorator生成器参数对象
 * @author guyadong
 *
 */
public class ThriftServiceDecoratorConfiguration extends GeneratorConfiguration 
					implements ThriftConstants{
	private static final String ENCODING = "UTF-8";
	/**
	 * 任务类型定义
	 * @author guyadong
	 *
	 */
	public static enum TaskType{
		/** 生成thrift service代码 */SERVICE("service", true),
		/** 生成thrift/swift client代码 */CLIENT("client", false),
		/** 生成基于Microsoft/thrifty client代码 */CLIENT_THRIFTY("client_thrifty", false),
		/** 生成eRPC代理服务代码 */ERPC_PROXY("erpc_proxy", false);
		/** 对应的模板文件路径前缀 */
		public final String folder;
		/** 是否转换引用类型 */
		public final boolean castReferType;
		private TaskType(String folder, boolean castReferType){
			this.folder = folder;
			this.castReferType = castReferType;
		}
	};
	/**
	 * 生成代码语言类型定义
	 * @author guyadong
	 *
	 */
	public static enum LanguageType{
		/** 生成thrift java代码 */JAVA(""),
		/** 生成thrift c++代码 */CPP("_cpp"),
		/** 生成thrift c++代码 */C_GLIB("_c_glib")
		;
		private static LanguageType current = null;
		/** 模板根文件夹名结尾 */
		public final String postfix;
		private LanguageType(String postfix){
			this.postfix = postfix;
		}
		/** 
		 * 返回对应的 {@link CodeWriter}实例
		 * @param outputFolder
		 */
		public CodeWriter getCodeWriter(File outputFolder){
			switch(this){
			case JAVA:
				return new JavaCodeWriter(outputFolder);
			case CPP:
				return new CxxCodeWriter(outputFolder);
			case C_GLIB:
				return new CxxCodeWriter(outputFolder);
			default:
					throw new UnsupportedOperationException("unsupported language type:" + this.name());
			}
		}
		public static LanguageType getCurrent() {
			return current;
		}
		public synchronized static void setCurrent(LanguageType current) {
			checkState(null == LanguageType.current 
					|| LanguageType.current.equals(current),
					"LanguageType.current can be initialized only onece");
			LanguageType.current = current;
		}
	};
	public static final String DEFAULT_TEMPLATE_FOLDER = "thrift";
	public static final String DEFAULT_LANGUAGE = "JAVA";
	/** refClass 的默认值 */
	public static final Class<?> DEF_REF_CLASS = Object.class;
	private static final String NO_REF_CLASS = "";
    private static final Map<String, String> defaultExcludeFields = ImmutableMap.of("gu.sql2java.BaseBean","modified,initialized");
    /** interface class -- reference class */
	private Map<Class<?>, Class<?>> interfaceClasses;
	/** interface class -- thrift service class */
	private Map<Class<?>, Class<?>> thriftServiceClasses;
	private TaskType taskType;
	private LanguageType languageType;
	private String thriftClientPackage;
	private String sourcepath;
	private String classpath;
	private final PropertiesConfiguration config = new PropertiesConfiguration();
	private Set<String> reqiredTags = Collections.emptySet();
	private Set<String> commonTypes = Collections.emptySet();
	private String programName;
	private String portPrefix;
	private Map<Class<?>, List<String>> excludeMethods = Maps.newHashMap();
	private Map<Class<?>, List<String>> includeMethods = Maps.newHashMap();

	private final Map<Class<?>, List<String>> excludeFields;
	private int erpcForwardPort;
	private int erpcProxyPort;
    private int defaultMaxLength=256;
    private int errmsgMaxLength=256;
	private int binaryOutputSize=256;
	private final List<Class<?>> extStructs;

	public static final ThriftServiceDecoratorConfiguration INSTANCE = new ThriftServiceDecoratorConfiguration();
	private ThriftServiceDecoratorConfiguration() {
		super();
		// 指定模板文件夹
		this.defaultValue.setProperty(TEMPLATE_FOLDER_OPTION_LONG, DEFAULT_TEMPLATE_FOLDER);
		// 指定refClass的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(REFERENCE_CLASS_OPTION_LONG,NO_REF_CLASS);
		// 指定refClass的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(THRIFT_CLIENT_PKG_OPTION_LONG,"");
		// 指定refClass的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(LANGUAGE_OPTION_LONG,DEFAULT_LANGUAGE);
		// 指定CONFIG的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(CONFIG_OPTION_LONG,null);
		// 指定sourcepath的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(SOURCE_PREFIX_OPTION_LONG,"");
		// 指定classpath的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(CLASS_PATH_OPTION_LONG,"");
		// 指定TAGS的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(TAGS_OPTION_LONG,"");
		// 指定COMMON_TYPES的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(COMMON_TYPES_OPTION_LONG,"");
		// 指定programName的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(ERPC_PROGRAM_OPTION_LONG,"");
		// 指定portPrefix的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(ERPC_PORT_PREFIX_OPTION_LONG,"");
		// 指定excludeMethods的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(EXCLUDE_METHODS_OPTION_LONG,new String[0]);
		// 指定includeMethods的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(INCLUDE_METHODS_OPTION_LONG,new String[0]);
		// 指定excludeFields的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(EXCLUDE_FIELDS_OPTION_LONG,new String[0]);

		// 指定excludeFields的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(THRIFT_SERVICE_CLASS_OPTION_LONG,"");

		excludeFields = Maps.newHashMap();
		// 指定erpcForwardPort的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(ERPC_FORWARD_PORT_OPTION_LONG,0);
		// 指定erpcProxyPort的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(ERPC_PROXY_PORT_OPTION_LONG,0);
		// 指定defaultMaxLength的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(ERPC_DEFAULT_MAX_LENGTH_OPTION_LONG,256);
		// 指定errmsgMaxLength的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(ERPC_ERRMSG_MAX_LENGTH_OPTION_LONG,256);
		// 指定binaryOutputSize的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(ERPC_BINARY_OUTPUT_SIZE_OPTION_LONG,256);
		// 指定binaryOutputSize的默认值，避免默认值为{@code null}
		this.defaultValue.setProperty(EXTENSIVE_STRUCT_OPTION_LONG,"");
		extStructs = Lists.newArrayList();
	}

	@Override
	public void loadConfig(Options options, CommandLine cmd) throws ParseException {
		super.loadConfig(options, cmd);
		try {
			List<Class<?>> interfaceList = toClassArray((String) getProperty(INTERFACE_CLASS_OPTION_LONG));
			List<Class<?>> refList = toClassArray((String) getProperty(REFERENCE_CLASS_OPTION_LONG));
			this.thriftClientPackage = (String)getProperty(THRIFT_CLIENT_PKG_OPTION_LONG);
			if(refList.size()>0 && refList.size() != interfaceList.size()){
				throw new ParseException("mismatch number interface class and reference class");
			}
			interfaceClasses = Maps.newLinkedHashMap();
			for(int i = 0 ;i < interfaceList.size() ; ++i){
				Class<?> key = interfaceList.get(i);
				if(interfaceList.get(i)!=DEF_REF_CLASS){
					try{
						Class<?> value = refList.get(i);
						interfaceClasses.put(key, value);
					}catch(IndexOutOfBoundsException e){
						interfaceClasses.put(key, DEF_REF_CLASS);
					}
				}
			}
			if(interfaceList.isEmpty()){
				throw new ParseException("NOT FOUND VALID interface class define");
			}
		} catch (ClassNotFoundException e) {
			throw new ParseException("ClassNotFoundException:"+e.getMessage());
		}
		try{
			taskType = TaskType.valueOf((String) getProperty(TASK_TYPE_OPTION_LONG));
			if((taskType == TaskType.CLIENT || taskType == TaskType.CLIENT_THRIFTY) && this.thriftClientPackage.isEmpty()){
				throw new IllegalArgumentException(String.format("must set param :%s",THRIFT_CLIENT_PKG_OPTION_LONG));
			}
		}catch(IllegalArgumentException e){
			throw new ParseException(e.getMessage());
		}
		try{
			languageType = LanguageType.valueOf((String) getProperty(LANGUAGE_OPTION_LONG));
			LanguageType.setCurrent(languageType);
		}catch(IllegalArgumentException e){
			throw new ParseException(e.getMessage());
		}
		if(hasProperty(CONFIG_OPTION_LONG)){
			File configFile = new File((String) getProperty(CONFIG_OPTION_LONG));
			checkArgument(configFile.isFile() && configFile.getName().endsWith(".properties"),
					"%s must be a .properties file",CONFIG_OPTION_LONG);
			try {
				// 指定文件编码方式,否则properties文件读取中文会是乱码,要求文件编码是UTF-8
			    FileBasedConfigurationBuilder.setDefaultEncoding(PropertiesConfiguration.class, ENCODING);
			    new FileHandler(config).load(configFile);
			} catch (Exception e) {
				Throwables.throwIfUnchecked(e);
				throw new RuntimeException(e);
			}
		}
		sourcepath = getProperty(SOURCE_PREFIX_OPTION_LONG);
		classpath = getProperty(CLASS_PATH_OPTION_LONG);
		// 允许 , 做分隔符
		classpath = classpath.replaceAll(",", File.pathSeparator);
		// linux下允许用 ;号做分隔符
		if(File.pathSeparatorChar  != ';'){
			classpath = classpath.replaceAll(";", File.pathSeparator);
		}
		JavadocReader.setSourcepath(sourcepath);
		JavadocReader.setClasspath(classpath);
		String tags = getProperty(TAGS_OPTION_LONG);
		if(!Strings.isNullOrEmpty(tags)){
			this.reqiredTags = Sets.newHashSet(MiscellaneousUtils.elementsOf(tags));
		}
		String types = getProperty(COMMON_TYPES_OPTION_LONG);
		if(!Strings.isNullOrEmpty(types)){
			this.commonTypes = Sets.newHashSet(MiscellaneousUtils.elementsOf(types));
		}
		String program = getProperty(ERPC_PROGRAM_OPTION_LONG);
		if(Strings.isNullOrEmpty(program)){
			ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY),ParseException.class,"must define argument: %s",ERPC_PROGRAM_OPTION_LONG);
		}else{
			this.programName = program;
		}
		String prefix = getProperty(ERPC_PORT_PREFIX_OPTION_LONG);
		if(Strings.isNullOrEmpty(prefix)){
			ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY),ParseException.class,"must define argument: %s",ERPC_PORT_PREFIX_OPTION_LONG);
		}else{
			this.portPrefix = prefix;
		}
		String[] excludeMethodNames = getProperty(EXCLUDE_METHODS_OPTION_LONG);
		if(excludeMethodNames != null){
			for(String str : excludeMethodNames){
				String[] entry = str.split(":");
				try {
					List<String> names = MiscellaneousUtils.elementsOf(entry[1]);
					if(!names.isEmpty()){
						excludeMethods.put(CodeGenUtils.forName(entry[0]),names);
					}
				} catch (ClassNotFoundException e) {
					SimpleLog.log(e.toString());
				}
			}
		}
		String[] inludeMethodNames = getProperty(INCLUDE_METHODS_OPTION_LONG);
		if(inludeMethodNames != null){
			for(String str : inludeMethodNames){
				String[] entry = str.split(":");
				try {
					List<String> names = MiscellaneousUtils.elementsOf(entry[1]);
					if(!names.isEmpty()){
						includeMethods.put(CodeGenUtils.forName(entry[0]),names);
					}
				} catch (ClassNotFoundException e) {
					SimpleLog.log(e.toString());
				}
			}
		}
		for(Entry<String, String> entry : defaultExcludeFields.entrySet()){
			try {
				excludeFields.put(CodeGenUtils.forName(entry.getKey()), MiscellaneousUtils.elementsOf(entry.getValue()));
			} catch (ClassNotFoundException e) {
				SimpleLog.log(e.toString());
			}
		}
		String[] excludeFieldsEntry = getProperty(EXCLUDE_FIELDS_OPTION_LONG);
		if(excludeFieldsEntry != null){
			for(String str : excludeFieldsEntry){
				String[] entry = str.split(":");
				try {
					List<String> names = MiscellaneousUtils.elementsOf(entry[1]);
					if(!names.isEmpty()){
						excludeFields.put(CodeGenUtils.forName(entry[0]),names);
					}
				} catch (ClassNotFoundException e) {
					SimpleLog.log(e.toString());
				}
			}
		}
		
		try {
			List<Class<?>> interfaceList = toClassArray((String) getProperty(INTERFACE_CLASS_OPTION_LONG));
			List<Class<?>> tsList = toClassArray((String) getProperty(THRIFT_SERVICE_CLASS_OPTION_LONG));
			ImmutableMap.Builder<Class<?>, Class<?>> builer= ImmutableMap.builder();
			if(!tsList.isEmpty()){ 
				ConditionChecks.checkTrue(tsList.size() == interfaceList.size(), 
						ParseException.class, 
						"mismatch number interface class and thrift service class");
				for(int i=0; i<tsList.size(); ++i){
					builer.put(interfaceList.get(i), tsList.get(i));
				}
			}else{
				ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY), 
						ParseException.class, 
						"must define argument: %s for ERPC_PROXY task",THRIFT_SERVICE_CLASS_OPTION_LONG);
			}
			thriftServiceClasses = builer.build();
		} catch (ClassNotFoundException e) {
			throw new ParseException("ClassNotFoundException:"+e.getMessage());
		}

		erpcForwardPort = ((Number)getProperty(ERPC_FORWARD_PORT_OPTION_LONG)).intValue();
		ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || erpcForwardPort>0, 
				ParseException.class, 
				"must define argument: %s for ERPC_PROXY task",ERPC_FORWARD_PORT_OPTION_LONG);
		
		erpcProxyPort = ((Number)getProperty(ERPC_PROXY_PORT_OPTION_LONG)).intValue();
		ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || erpcProxyPort>0, 
				ParseException.class, 
				"must define argument: %s for ERPC_PROXY task",ERPC_PROXY_PORT_OPTION_LONG);

		defaultMaxLength = ((Number)getProperty(ERPC_DEFAULT_MAX_LENGTH_OPTION_LONG)).intValue();
		ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || defaultMaxLength>0, 
				ParseException.class, 
				"must define argument: %s for ERPC_PROXY task",ERPC_DEFAULT_MAX_LENGTH_OPTION_LONG);

		errmsgMaxLength = ((Number)getProperty(ERPC_ERRMSG_MAX_LENGTH_OPTION_LONG)).intValue();
		ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || errmsgMaxLength>0, 
				ParseException.class, 
				"must define argument: %s for ERPC_PROXY task",ERPC_ERRMSG_MAX_LENGTH_OPTION_LONG);
		
		binaryOutputSize = ((Number)getProperty(ERPC_BINARY_OUTPUT_SIZE_OPTION_LONG)).intValue();
		ConditionChecks.checkTrue(!taskType.equals(TaskType.ERPC_PROXY) || binaryOutputSize>0, 
				ParseException.class, 
				"must define argument: %s for ERPC_PROXY task",ERPC_BINARY_OUTPUT_SIZE_OPTION_LONG);

		String extStructNames = getProperty(EXTENSIVE_STRUCT_OPTION_LONG);
		if(extStructNames != null){
			for(String str : MiscellaneousUtils.elementsOf(extStructNames)){
				try {
					Class<?> clazz = CodeGenUtils.forName(str);
					if(isThriftStruct(clazz)){
						extStructs.add(clazz);
					}
				} catch (ClassNotFoundException e) {
					SimpleLog.log(e.toString());
				}
			}
		}
	}
	
	private List<Class<?>> toClassArray(String input) throws ClassNotFoundException{
		String[] classNames = input.split(",");
		List<Class<?>>result = Lists.newArrayList(); 
		for(String name:classNames){
			result.add(name.isEmpty()? DEF_REF_CLASS : CodeGenUtils.forName(name));
		}
		return result;
	}

	public Map<Class<?>, Class<?>> getInterfaceClasses() {
		return interfaceClasses;
	}

	public Map<Class<?>, Class<?>> getThriftServiceClasses() {
		return thriftServiceClasses;
	}

	public TaskType getTaskType() {
		return taskType;
	}

	public LanguageType getLanguageType() {
		return languageType;
	}

	@Override
	public String getTemplateFolder() {
		String folder = super.getTemplateFolder();
		StringBuilder sb = new StringBuilder(folder);		
		if(DEFAULT_TEMPLATE_FOLDER.equals(folder)){
			sb.append(getLanguageType().postfix);	
		}		
		sb.append("/").append(getTaskType().folder);
		return sb.toString();
	}

	public String getThriftClientPackage() {
		return thriftClientPackage;
	}
	public CodeWriter getCodeWriter(){
		return this.getLanguageType().getCodeWriter(getOutputLocation());
	}

	/**
	 * @return config
	 */
	public Configuration getConfig() {
		return config;
	}
	/**
	 * @return sourcepath
	 */
	public String getSourcepath() {
		return sourcepath;
	}

	/**
	 * @return classpath
	 */
	public String getClasspath() {
		return classpath;
	}

	public Set<String> getReqiredTags() {
		return reqiredTags;
	}

	public Set<String> getCommonTypes() {
		return commonTypes;
	}

	/**
	 * @return programName
	 */
	public String getProgramName() {
		return programName;
	}

	/**
	 * @return portPrefix
	 */
	public String getPortPrefix() {
		return portPrefix;
	}

	/**
	 * @return excludeMethods
	 */
	@Override
	public Map<Class<?>, List<String>> getExcludeMethods() {
		return excludeMethods;
	}

	/**
	 * @return includeMethods
	 */
	@Override
	public Map<Class<?>, List<String>> getIncludeMethods() {
		return includeMethods;
	}

	/**
	 * @return excludeFields
	 */
	public Map<Class<?>, List<String>> getExcludeFields() {
		return excludeFields;
	}

	/**
	 * @return erpcForwardPort
	 */
	public int getErpcForwardPort() {
		return erpcForwardPort;
	}

	/**
	 * @return erpcProxyPort
	 */
	public int getErpcProxyPort() {
		return erpcProxyPort;
	}

	/**
	 * @return defaultMaxLength
	 */
	public int getDefaultMaxLength() {
		return defaultMaxLength;
	}

	/**
	 * @return errmsgMaxLength
	 */
	public int getErrmsgMaxLength() {
		return errmsgMaxLength;
	}

	/**
	 * @return binaryOutputSize
	 */
	public int getBinaryOutputSize() {
		return binaryOutputSize;
	}

	public List<Class<?>> getExtStructs() {
		return extStructs;
	}
	
}
